<template>
  <div class="drawing-board">
    <canvas id="canvas" @mousedown="handleDown"/>
  </div>
</template>

<script>
import image from './5646.jpeg'
export default {
  name: 'DrawingBoard',
  data() {
    return {
      canvasInfo: {
        canvas: {},
        canvasSize: {
          width: 2000,
          height: 600
        },
        ctx: {},
        type: 'mosaic', // curve line rect ellipse arrow mosaic
        lineColor: 'red',
        lineWidth: 25,
        offset: 5,
        mosaicSize: 20,
        imgData: {},
        clearRect: {
          x: 0,
          y: 0,
          width: 10,
          height: 10
        }
      }
    }
  },
  mounted() {
    this.setCanvasInfo()
  },
  methods: {
    setCanvasInfo() {
      this.canvasInfo.canvas = document.getElementById('canvas')
      this.canvasInfo.canvas.width = this.canvasInfo.canvasSize.width
      this.canvasInfo.canvas.height = this.canvasInfo.canvasSize.height
      this.canvasInfo.ctx = this.canvasInfo.canvas.getContext('2d')
      const img = new Image()
      img.src = image
      img.onload = () => {
        console.log('图片加载完成=====================')
        this.canvasInfo.ctx.drawImage(img, 0, 0)
      }
    },
    handleDown(e) {
      const dom = e.target
      const domRect = dom.getBoundingClientRect()
      let canvasX = e.clientX - domRect.x - Math.round(this.canvasInfo.lineWidth / 2)
      let canvasY = e.clientY - domRect.y - Math.round(this.canvasInfo.lineWidth / 2)
      this.startDraw()

      const move = moveE => {
        const mX = moveE.clientX - domRect.x - Math.round(this.canvasInfo.lineWidth / 2)
        const mY = moveE.clientY - domRect.y - Math.round(this.canvasInfo.lineWidth / 2)

        const { type } = this.canvasInfo
        if (type === 'curve') {
          this.drawCurve({ newX: mX, newY: mY, oldX: canvasX, oldY: canvasY })
          canvasX = mX
          canvasY = mY
        }
        if (type === 'line') {
          this.drawLine({ newX: mX, newY: mY, oldX: canvasX, oldY: canvasY })
        }
        if (type === 'rect') {
          this.drawRect({ newX: mX, newY: mY, oldX: canvasX, oldY: canvasY })
        }
        if (type === 'ellipse') {
          this.drawEllipse({ newX: mX, newY: mY, oldX: canvasX, oldY: canvasY })
        }
        if (type === 'arrow') {
          this.drawArrow({ newX: mX, newY: mY, oldX: canvasX, oldY: canvasY })
        }
        if (type === 'mosaic') {
          if (Math.pow(canvasX - mX, 2) >= Math.pow(this.canvasInfo.mosaicSize, 2) || Math.pow(canvasY - mY, 2) >= Math.pow(this.canvasInfo.mosaicSize, 2)) {
            this.drawMosaic({ newX: mX, newY: mY, oldX: canvasX, oldY: canvasY })
            canvasX = mX
            canvasY = mY
          }
        }
      }

      const end = () => {
        this.endDraw()
        dom.removeEventListener('mousemove', move)
        document.removeEventListener('mouseup', end)
      }

      dom.addEventListener('mousemove', move)
      document.addEventListener('mouseup', end)
    },
    startDraw() {
      const { ctx, lineColor, lineWidth, type } = this.canvasInfo
      if (type === 'curve') {
        ctx.beginPath()
        ctx.strokeStyle = lineColor
        ctx.lineWidth = lineWidth
        ctx.lineCap = 'round'
        ctx.lineJoin = 'round'
      }
      if (type === 'mosaic') {
        this.canvasInfo.imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height)
      }
    },
    endDraw() {
      const { ctx, type } = this.canvasInfo
      if (type === 'curve') {
        ctx.closePath()
      }
    },
    // 画曲线
    drawCurve({ oldX, oldY, newX, newY }) {
      const { ctx } = this.canvasInfo
      ctx.moveTo(oldX, oldY)
      ctx.lineTo(newX, newY)
      ctx.stroke()
    },
    // 画直线
    drawLine({ oldX, oldY, newX, newY }) {
      const { ctx, lineColor, lineWidth, clearRect } = this.canvasInfo
      ctx.clearRect(clearRect.x, clearRect.y, clearRect.width, clearRect.height)
      this.setClearRectData({ oldX, oldY, newX, newY })
      ctx.beginPath()
      ctx.strokeStyle = lineColor
      ctx.lineWidth = lineWidth
      ctx.lineCap = 'round'
      ctx.lineJoin = 'round'
      ctx.moveTo(oldX, oldY)
      ctx.lineTo(newX, newY)
      ctx.stroke()
      ctx.closePath()
    },
    // 画矩形
    drawRect({ oldX, oldY, newX, newY }) {
      const { ctx, lineColor, lineWidth, clearRect, offset } = this.canvasInfo
      ctx.clearRect(clearRect.x, clearRect.y, clearRect.width, clearRect.height)
      const newClearRect = this.setClearRectData({ oldX, oldY, newX, newY })

      ctx.beginPath()
      ctx.strokeStyle = lineColor
      ctx.lineWidth = lineWidth
      ctx.lineCap = 'round'
      ctx.lineJoin = 'round'

      ctx.strokeRect(newClearRect.x + offset, newClearRect.y + offset, newClearRect.width - offset * 2, newClearRect.height - offset * 2)
      ctx.closePath()
    },
    // 画椭圆
    drawEllipse({ oldX, oldY, newX, newY }) {
      const { ctx, lineColor, lineWidth, clearRect, offset } = this.canvasInfo
      ctx.clearRect(clearRect.x, clearRect.y, clearRect.width, clearRect.height)
      const newClearRect = this.setClearRectData({ oldX, oldY, newX, newY })
      const radiusX = (newClearRect.width - offset * 2) / 2
      const radiusY = (newClearRect.height - offset * 2) / 2
      const ellipseJSON = {
        x: newClearRect.x + offset + radiusX,
        y: newClearRect.y + offset + radiusY,
        radiusX,
        radiusY
      }
      ctx.beginPath()
      ctx.strokeStyle = lineColor
      ctx.lineWidth = lineWidth
      ctx.lineCap = 'round'
      ctx.lineJoin = 'round'
      ctx.ellipse(ellipseJSON.x, ellipseJSON.y, ellipseJSON.radiusX, ellipseJSON.radiusY, 0, 0, 2 * Math.PI)
      ctx.stroke()
      ctx.closePath()
    },
    // 画箭头
    drawArrow({ oldX, oldY, newX, newY }) {
      const { ctx, lineColor, lineWidth, clearRect } = this.canvasInfo
      ctx.clearRect(clearRect.x, clearRect.y, clearRect.width, clearRect.height)
      this.setClearRectData({ oldX, oldY, newX, newY }, lineWidth)
      ctx.arrow({ x: oldX, y: oldY }, { x: newX, y: newY }, lineColor, lineWidth)
    },
    drawMosaic({ oldX, oldY, newX, newY }) {
      const { ctx, lineWidth, imgData, mosaicSize } = this.canvasInfo
      ctx.mosaic(imgData.data, { x: oldX, y: oldY }, { x: newX, y: newY }, lineWidth, mosaicSize)
    },
    // 设置清空的矩形框数据
    setClearRectData({ oldX, oldY, newX, newY }, offset = 5) {
      const x = oldX <= newX ? oldX : newX
      const y = oldY <= newY ? oldY : newY
      const width = Math.abs(newX - oldX)
      const height = Math.abs(newY - oldY)
      const clearRect = {
        x: x - offset,
        y: y - offset,
        width: width + offset * 2,
        height: height + offset * 2
      }
      this.canvasInfo.clearRect = clearRect
      return clearRect
    }
  }
}

/**
 * @param beginPoint 开始节点
 * @param stopPoint 结束节点
 * @param color 填充颜色
 * @param edgeLen 三角形斜边长度
 * @param edgeAngle 三角形斜边角度
 * */
CanvasRenderingContext2D.prototype.arrow = function(beginPoint, stopPoint, color, edgeLen, edgeAngle = 30) {
  const ctx = this
  const polygonVertex = []
  const arrow = {
    angle: 0,
    // 在CONST中定义的edgeLen以及angle参数
    // 短距离画箭头的时候会出现箭头头部过大，修改：
    dynArrowSize: function(beginPoint, stopPoint) {
      const x = stopPoint.x - beginPoint.x
      const y = stopPoint.y - beginPoint.y
      const length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
      if (length < 120) {
        edgeLen = edgeLen * 0.8
        edgeAngle = edgeAngle * 0.8
        return
      }
      if (length < 150) {
        edgeLen = edgeLen * length / 150
        edgeAngle = edgeAngle * length / 150
      }
    },

    // getRadian 返回以起点与X轴之间的夹角角度值
    getRadian: function(beginPoint, stopPoint) {
      this.angle = Math.atan2(stopPoint.y - beginPoint.y, stopPoint.x - beginPoint.x) / Math.PI * 180
      this.dynArrowSize(beginPoint, stopPoint)
    },

    // 获得箭头底边两个点
    arrowCoord: function(beginPoint, stopPoint) {
      polygonVertex[0] = beginPoint.x
      polygonVertex[1] = beginPoint.y
      polygonVertex[6] = stopPoint.x
      polygonVertex[7] = stopPoint.y
      this.getRadian(beginPoint, stopPoint)
      polygonVertex[8] = stopPoint.x - edgeLen * Math.cos(Math.PI / 180 * (this.angle + edgeAngle))
      polygonVertex[9] = stopPoint.y - edgeLen * Math.sin(Math.PI / 180 * (this.angle + edgeAngle))
      polygonVertex[4] = stopPoint.x - edgeLen * Math.cos(Math.PI / 180 * (this.angle - edgeAngle))
      polygonVertex[5] = stopPoint.y - edgeLen * Math.sin(Math.PI / 180 * (this.angle - edgeAngle))
    },

    // 获取另两个底边侧面点
    sideCoord: function() {
      const midpoint = {}
      midpoint.x = (polygonVertex[4] + polygonVertex[8]) / 2
      midpoint.y = (polygonVertex[5] + polygonVertex[9]) / 2
      polygonVertex[2] = (polygonVertex[4] + midpoint.x) / 2
      polygonVertex[3] = (polygonVertex[5] + midpoint.y) / 2
      polygonVertex[10] = (polygonVertex[8] + midpoint.x) / 2
      polygonVertex[11] = (polygonVertex[9] + midpoint.y) / 2
    },

    // 画箭头
    drawArrow: function() {
      ctx.fillStyle = color
      ctx.beginPath()
      ctx.moveTo(polygonVertex[0], polygonVertex[1])
      ctx.lineTo(polygonVertex[2], polygonVertex[3])
      ctx.lineTo(polygonVertex[4], polygonVertex[5])
      ctx.lineTo(polygonVertex[6], polygonVertex[7])
      ctx.lineTo(polygonVertex[8], polygonVertex[9])
      ctx.lineTo(polygonVertex[10], polygonVertex[11])
      ctx.closePath()
      ctx.fill()
    },
    init: function() {
      this.arrowCoord(beginPoint, stopPoint)
      this.sideCoord()
      this.drawArrow()
    }
  }

  arrow.init()
}

/**
 * @param imageData 图片的点数据
 * @param beginPoint 开始节点
 * @param stopPoint 结束节点
 * @param size 马赛克画笔大小
 * @param mosaicSize 马赛克大小
 * */
CanvasRenderingContext2D.prototype.mosaic = function(imageData, beginPoint, stopPoint, size = 10, mosaicSize = 10) {
  const ctx = this
  const positionX = beginPoint.x <= stopPoint.x ? beginPoint.x : stopPoint.x
  const positionY = beginPoint.y <= stopPoint.y ? beginPoint.y : stopPoint.y
  const width = Math.abs(beginPoint.x - stopPoint.x)
  const height = Math.abs(beginPoint.y - stopPoint.y)

  const newHeight = positionY + (height > size ? height : size)
  const newWidth = positionX + (width > size ? width : size)
  for (let y = positionY; y < newHeight; y += mosaicSize) {
    for (let x = positionX; x < newWidth; x += mosaicSize) {
      const r = imageData[(y * ctx.canvas.width + x + mosaicSize / 2) * 4]
      const g = imageData[(y * ctx.canvas.width + x + mosaicSize / 2) * 4 + 1]
      const b = imageData[(y * ctx.canvas.width + x + mosaicSize / 2) * 4 + 2]

      ctx.fillStyle = `rgb(${r}, ${g}, ${b})`
      if (x + mosaicSize <= positionX + size && y + mosaicSize <= positionY + size) {
        ctx.fillRect(x, y, mosaicSize, mosaicSize)
      } else {
        if (x + mosaicSize > positionX + size) {
          ctx.fillRect(positionX + size - mosaicSize, y, mosaicSize, mosaicSize)
        }
        if (y + mosaicSize > positionY + size) {
          ctx.fillRect(x, positionY + size - mosaicSize, mosaicSize, mosaicSize)
        }
      }
    }
  }
}

</script>

<style lang="scss" scoped>
.drawing-board {
  width: 100%;
  height: 100%;
  background: red;

  #canvas {
    background: #fff;
  }
}
</style>
